using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows;
using Rhizome.Bindings;
using Rhizome.Contract;
using Rhizome.Contract.Diag;
using Rhizome.Contract.Visitor;
using Rhizome.Internal;

namespace Rhizome.Model
{
	public abstract class RhizomeElement : IElement, IElementContainer
	{
		public static readonly RhizomeProperty<IEventSink> EventSyncProperty =
			RhizomeProperty<IEventSink>.Register("EventSink", true, (IEventSink)null);

		public static readonly RhizomeProperty<IMessageSink> MessageSyncProperty =
			RhizomeProperty<IMessageSink>.Register("MessageSink", true, (IMessageSink)null);

		/// <summary>
		/// Automaton SynchronizationContext
		/// </summary>
		public static readonly RhizomeProperty<SynchronizationContext> SynchronizationContextProperty =
			RhizomeProperty<SynchronizationContext>.Register("SynchronizationContext", true, (SynchronizationContext)null);

		public static readonly RhizomeProperty<object> PlantProperty =
			RhizomeProperty<object>.Register("Plant", true, (object)null);

		public static readonly RhizomeProperty<IActiveObjectHost> ActiveObjectHostProperty =
			RhizomeProperty<IActiveObjectHost>.Register(
				"ActiveObjectHost", true, 
				() => new ActiveObjectHost
				      	{
				      		IsBackground = true,
#if !WINDOWS_PHONE && !SILVERLIGHT
                        ApartmentState = ApartmentState.STA,
#endif
				      		ThreadName = "ActiveObjectHost (default)"
				      	});

		public static readonly RhizomeProperty<IAction> ActionProperty =
			RhizomeProperty<IAction>.Register("Action", true, (IAction)null);

		public static readonly RhizomeProperty<ICriterion> CriterionProperty =
			RhizomeProperty<ICriterion>.Register("Criterion", true, (ICriterion)null);

		public static readonly RhizomeProperty<BindingProxy> BindingProperty =
			RhizomeProperty<BindingProxy>.Register("Binding", true, (BindingProxy)null);

		private Dictionary<object, object> _properties;

		private Dictionary<object, object> Properties
		{
			get
			{
				if (_properties == null)
					Interlocked.CompareExchange(ref _properties, new Dictionary<object, object>(), null);

				return _properties;
			}
		}

		protected internal RhizomeElement Parent { get; set; }

		protected ILogger Logger
		{
			[DebuggerStepThrough]
			get { return GetValue(Model.Logger.LoggerProperty); }
		}

		public void SetValue<T>(RhizomeProperty<T> property, T value)
		{
			lock (Properties)
			{
				if (Properties.ContainsKey(property))
					Properties[property] = value;
				else Properties.Add(property, value);
			}

			var r = value as RhizomeElement;
			if (r != null && r.Parent == null) r.Parent = this;
		}

		public virtual T GetValue<T>(RhizomeProperty<T> property)
		{
			return 
				ReferenceEquals(property, PlantProperty) 
					? (T) GetPlantProperty() 
					: InternalGetValue(property);
		}

		private object GetPlantProperty()
		{
			object value;
			if (TryGetInternalValue(PlantProperty, out value) == false)
				return Parent == null ? null : Parent.GetValue(PlantProperty);

			if (Parent == null) return value;

			var path = value as string;
			if (path == null) return value;

			var parentValue = Parent.GetValue(PlantProperty);
			if (ReferenceEquals(parentValue, null))
				throw new InvalidOperationException("Parent Plant is null");

			var type = parentValue.GetType();
			var propertyInfo = type.GetProperty(path);
			if (propertyInfo == null)
				throw new InvalidOperationException("Property {0}.{1} not found".With(type, path));

			value = propertyInfo.GetValue(parentValue, null);
			SetValue(PlantProperty, value);
			return value;
		}

		private T InternalGetValue<T>(RhizomeProperty<T> property)
		{
			T value;
			if (TryGetInternalValue(property, out value))
				return value;

			return
				Parent == null || property.Inheritable == false
					? property.DefaultValue
					: Parent.GetValue(property);
		}

		protected bool TryGetInternalValue<T>(RhizomeProperty<T> property, out T value)
		{
			if (_properties != null)
			{
				lock (_properties)
				{
					object v;
					if (_properties.TryGetValue(property, out v))
					{
						value = (T) v;
						return true;
					}
				}
			}

			value = default(T);
			return false;
		}


		protected void UnsetValue<T>(RhizomeProperty<T> property)
		{
			if (_properties != null)
				lock (_properties) _properties.Remove(property);
		}

		/// <summary>
		/// Dispatches an asynchronous message to a Automaton synchronization context.
		/// If synchronization context not set, invokes action on current thread
		/// </summary>
		/// <param name="action"></param>
		protected void Post(Action action)
		{
			var synchronizationContext = GetValue(SynchronizationContextProperty);
			if (synchronizationContext == null)
				action();
			else
				synchronizationContext.Post(state => action(), null);
		}

		/// <summary>
		/// Dispatches an synchronous message to a Automaton synchronization context.
		/// If synchronization context not set, invokes action on current thread
		/// </summary>
		/// <param name="action"></param>
		protected void Send(Action action)
		{
			var synchronizationContext = GetValue(SynchronizationContextProperty);
			if (synchronizationContext == null)
				action();
			else
				synchronizationContext.Send(state => action(), null);
		}

		/// <summary>
		/// Dispatches an synchronous message to a Automaton synchronization context.
		/// If synchronization context not set, invokes action on current thread
		/// </summary>
		protected T Send<T>(Func<T> func)
		{
			var synchronizationContext = GetValue(SynchronizationContextProperty);
			if (synchronizationContext == null)
				return func();

			var result = default(T);
			synchronizationContext.Send(state => result = func(), null);
			return result;
		}

		#region Implementation of IElement

		public virtual void Visit(IVisitor visitor)
		{
			visitor.Accept(this);
		}

		#endregion

		#region Implementation of IElementContainer

		public virtual IEnumerable<IElement> GetElements()
		{
			var fromCollections = 
				from collection in GetElementCollections()
			    from e in collection.OfType<IElement>()
			    select e;

			var fromLocal = GetLocalElements().OfType<IElement>();

		    var result = fromCollections.Concat(fromLocal);

            if (_properties != null)
            {
                lock (_properties) result = result.Concat(_properties.Values.OfType<IElement>().ToArray());
            }
		    return result;
		}

		protected virtual IEnumerable<IEnumerable> GetElementCollections()
		{
			if (_properties != null) yield return _properties;
		}

		protected virtual IEnumerable GetLocalElements()
		{
			yield break;
		}

		#endregion
	}
}